%{
#include <stdio.h>
#include <string.h>
#include "yacc.tab.h"
#include "symtable.h"
#include "ast_node.h"
#include "parser.h"
#include <unistd.h>
#include "structtable.h"
#include <stdlib.h>
 
// line number (got from lex.l)
extern int linenum;

// buffer for general use
#define DD_BUFFER_SIZE 1000
char buffer[DD_BUFFER_SIZE];

// error
void yyerror(const char *str)
{
        fprintf(stderr,"error: line %d: %s\n", linenum, str);
	_exit(-1);
}
 
// game node, parent of all nodes
struct ast_node *game_node;

/* compiler flags
 * debug_symbol_table: print details of the symbol table
 * debug_struct_table: print details of the struct table
 * debug_ast: print the abstract syntax tree
 */
int debug_symbol_table = 0;
int debug_struct_table = 0;
int debug_ast = 0;
char *srcfile = 0;

// init data, parse, exit
int main(int argc, char *argv[])
{

	/* handle arguments
	 * currently they can only allow printing of debugging information
	 */
	int i = 1;
	while (i < argc) {

		if (strcmp(argv[i], "--debug") == 0) {
			debug_symbol_table = 1;
			debug_struct_table = 1;
			debug_ast = 1;
		}
		else if (strcmp(argv[i], "--debug-symbol-table") == 0) {
			debug_symbol_table = 1;
		}
		else if (strcmp(argv[i], "--debug-struct-table") == 0) {
			debug_struct_table = 1;
		}
		else if (strcmp(argv[i], "--debug-ast") == 0) {
			debug_ast = 1;
		}
		// no specific argument found, assume that's source file
		else {
			srcfile = argv[i];
		}

		// next argument
		i++;

	} // done parsing arguments

	if (srcfile == 0) {
		printf("please supply a source file!\n");
		return -1;
	}

	// init data
	linenum = 1;

	// initial symbols
	if (!symtable_init()) {
		fprintf(stderr, "yacc: unable to initialise symbol table\n");
		return -1;
	}
	if (symtable_insert("DD_WIDTH", DD_INTERNAL_WIDTH) == -1
	||  symtable_insert("DD_HEIGHT", DD_INTERNAL_HEIGHT) == -1) {
		fprintf(stderr, "yacc: unable to add symbol to symbol table\n");
		return -1;
	}

	game_node = ast_create(AST_GAME, 0);

	// init structs
	struct struct_entry *temp_entry = malloc(sizeof(struct struct_entry));
	temp_entry->name = "dd_world";
	struct_insert(temp_entry);
	temp_entry = malloc(sizeof(struct struct_entry));
	temp_entry->name = "dd_sprite";
	struct_insert(temp_entry);
	temp_entry = malloc(sizeof(struct struct_entry));
	temp_entry->name = "dd_vector2d";
	struct_insert(temp_entry);

	// parse!
        yyparse();

	// parse resulting ast tree to a file
	parse(argv[1], game_node);

	// print debug data
	if (debug_symbol_table) {
		symtable_print();
	}
	if (debug_struct_table) {
		struct_print();
	}
	if (debug_ast) {
		ast_print(game_node);
	}

	// clean everything
	symtable_clean();
	ast_delete(game_node);

	// success!
	return 0;
} 

%}

/* keywords */
%token DD_KEYWORD_INT DD_KEYWORD_FLOAT DD_KEYWORD_STRUCT DD_KEYWORD_OVERRIDE DD_KEYWORD_IF DD_KEYWORD_VOID

/* internal variables */
%token DD_INTERNAL_WIDTH DD_INTERNAL_HEIGHT

/* constants */
%token DD_CONSTANT_SYMBOL DD_CONSTANT_STRING DD_CONSTANT_NUMBER

/* operators */
%token DD_OPERATOR_EQ DD_OPERATOR_LE DD_OPERATOR_GE

/* associativity */
%left '='
%left '+' '-'
%left '*' '/'
%left '>' '<' DD_OPERATOR_EQ DD_OPERATOR_LE DD_OPERATOR_GE

%%

/* each rule creates a node,
 * possibly with children nodes,
 * all non-terminals are nodes that can be obtained with ast_pop() (left to right)
 */

/* the game itself, contains statements
 */
game:
	statements {
		ast_child_add(game_node, ast_pop());
	}
	;

statements:
	statement statements_additional {
		// add statement to group
		struct ast_node *group = ast_pop();
		struct ast_node *statement = ast_pop();

		ast_child_add_first(group, statement);
		ast_push(group);
	}
	;

statements_additional:
	{ ast_push( ast_create(AST_GROUP, 0) ); }
	| statement statements_additional {
		// add statement to group
		struct ast_node *group = ast_pop();
		struct ast_node *statement = ast_pop();

		ast_child_add_first(group, statement);
		ast_push(group);
	}
	;

statement:

	/* definition statement
	 */
	definition
	|
	/* define struct
	 * takes all definitions, and adds them to the struct table
	 */
	DD_KEYWORD_STRUCT DD_CONSTANT_SYMBOL struct_parent '{' definitions '}' ';' {

		// get symbol in symbol table
		struct entry *e = symtable_entryat($2);
		if (!e) {
			fprintf(stderr, "yacc: struct definition: could not find symtable entry at %d\n", $2);
			exit(-1);
		}

		// check if its already defined, if not define it, if yes error
		if (e->token == DD_CONSTANT_SYMBOL) {
			e->token = DD_KEYWORD_STRUCT;
		}
		else {
			snprintf(buffer, DD_BUFFER_SIZE, "'%s' is already defined", e->lexptr);
			yyerror(buffer);
		}

		// create new struct entry based on symbol table
		struct struct_entry se;
		se.name = e->lexptr;
		se.parent = 0;

		// group node has all definitions
		struct ast_node *group = ast_pop();

		// if parent exists
		struct ast_node *parent = ast_pop();
		if (parent->node_type == AST_STRUCT_PARENT) {
			se.parent = parent->value;
		}
		else {
			se.parent = -1;
		}

		int struct_index = struct_insert(&se);

		// create struct node and add definitions as child
		/*
		group->node_type = AST_STRUCT;
		group->value = struct_index;
		*/
		struct ast_node *n = ast_create(AST_STRUCT, struct_index);
		ast_child_add(n, group);

		// push to ast tree
		ast_push(n);
	}
	|
	/* if statement */
	DD_KEYWORD_IF '(' expression ')' '{' statements '}' {
		struct ast_node *expression;
		struct ast_node *statements;
		struct ast_node *ifnode = ast_create(AST_IF, 0);

		statements = ast_pop();
		expression = ast_pop();

		ast_child_add(ifnode, expression);
		ast_child_add(ifnode, statements);

		ast_push(ifnode);
	}
	|
	expression ';' {
		struct ast_node *group = ast_create(AST_GROUP_STATEMENTS, 0);
		ast_child_add(group, ast_pop());
		ast_push(group);
	}
	;

definitions:
	{ ast_push( ast_create(AST_GROUP, 0) ); }
	| definitions definition {
		struct ast_node *n;
		struct ast_node *c;

		c = ast_pop();
		n = ast_pop();

		ast_child_add(n, c);
		ast_push(n);
	}
	;

definition:
	/* definition
	 * define a new variable
	 * error if variable is already defined
	 */
	variable_type DD_CONSTANT_SYMBOL ';' {

		// get variable type
		struct ast_node *vartype = ast_pop();

		// get symbol in symbol table
		struct entry *e = symtable_entryat($2);
		if (!e) {
			fprintf(stderr, "yacc: variable definition: could not find symtable entry at %d\n", $2);
			exit(-1);
		}

		// check if its already defined, if not define it, if yes error
		if (e->token == DD_CONSTANT_SYMBOL) {

			// variable type is void
			if (vartype->node_type == AST_VARTYPE_VOID) {
				e->token = DD_KEYWORD_VOID;
			}
			else
			// variable type is an int
			if (vartype->node_type == AST_VARTYPE_INT) {
				e->token = DD_KEYWORD_INT;
			}
			else
			// variable type is a float
			if (vartype->node_type == AST_VARTYPE_FLOAT) {
				e->token = DD_KEYWORD_FLOAT;
			}
			else
			// variable type is a struct
			if (vartype->node_type == AST_VARTYPE_STRUCT) {
				e->token = DD_KEYWORD_STRUCT;
				e->value = vartype->value;
			}
		}
		else {
			snprintf(buffer, DD_BUFFER_SIZE, "'%s' is already defined", e->lexptr);
			yyerror(buffer);
		}

		// create a definition node
		struct ast_node *n = ast_create(AST_DEFINITION, 0);

		// that has the to-be defined variable as child
		struct ast_node *c = ast_create(AST_IDENTIFIER, $2);
		ast_child_add(n, c);

		// add it to the game
		ast_push(n);
	}
	|
	/* function definition */
	variable_type DD_CONSTANT_SYMBOL '(' function_arguments_decleration ')' '{' statements '}' ';' {
		// get variable type
		struct ast_node *statements = ast_pop();
		struct ast_node *arguments = ast_pop();
		struct ast_node *vartype = ast_pop();

		struct entry *function = symtable_entryat($2);
		if (!function) {
			fprintf(stderr, "yacc: function definition: could not find symtable entry at %d\n", $2);
			exit(-1);
		}
		function->value = 0;

		struct ast_node *n = ast_create(AST_FUNCTION_DEFINITION, $2);
		ast_child_add(n, arguments);
		ast_child_add(n, statements);
		ast_push(n);
	}
	|
	DD_KEYWORD_OVERRIDE variable_type DD_CONSTANT_SYMBOL '(' function_arguments_decleration ')' '{' statements '}' ';' {
		// get variable type
		struct ast_node *statements = ast_pop();
		struct ast_node *arguments = ast_pop();
		struct ast_node *vartype = ast_pop();

		struct entry *function = symtable_entryat($3);
		if (!function) {
			fprintf(stderr, "yacc: override function definition: could not find symtable entry at %d\n", $3);
			exit(-1);
		}
		function->value = 1;

		struct ast_node *n = ast_create(AST_FUNCTION_DEFINITION, $3);
		ast_child_add(n, arguments);
		ast_child_add(n, statements);
		ast_push(n);
	}
	;

expression:
	expression '=' expression {
		struct ast_node *n = ast_create(AST_ASSIGNMENT, '=');
		struct ast_node *exp = ast_pop();
		struct ast_node *term = ast_pop();
		ast_child_add(n, term);
		ast_child_add(n, exp);
		ast_push(n);
	}
	|
	expression '>' expression {
		struct ast_node *n = ast_create(AST_OPERATOR_BINARY, '>');
		struct ast_node *exp = ast_pop();
		struct ast_node *term = ast_pop();
		ast_child_add(n, term);
		ast_child_add(n, exp);
		ast_push(n);
	}
	|
	expression '<' expression {
		struct ast_node *n = ast_create(AST_OPERATOR_BINARY, '<');
		struct ast_node *exp = ast_pop();
		struct ast_node *term = ast_pop();
		ast_child_add(n, term);
		ast_child_add(n, exp);
		ast_push(n);
	}
	|
	expression DD_OPERATOR_EQ expression {
		struct ast_node *n = ast_create(AST_OPERATOR_BINARY, DD_OPERATOR_EQ);
		struct ast_node *exp = ast_pop();
		struct ast_node *term = ast_pop();
		ast_child_add(n, term);
		ast_child_add(n, exp);
		ast_push(n);
	}
	|
	expression DD_OPERATOR_LE expression {
		struct ast_node *n = ast_create(AST_OPERATOR_BINARY, DD_OPERATOR_LE);
		struct ast_node *exp = ast_pop();
		struct ast_node *term = ast_pop();
		ast_child_add(n, term);
		ast_child_add(n, exp);
		ast_push(n);
	}
	|
	expression DD_OPERATOR_GE expression {
		struct ast_node *n = ast_create(AST_OPERATOR_BINARY, DD_OPERATOR_GE);
		struct ast_node *exp = ast_pop();
		struct ast_node *term = ast_pop();
		ast_child_add(n, term);
		ast_child_add(n, exp);
		ast_push(n);
	}
	|
	expression '+' expression {
		struct ast_node *n = ast_create( AST_OPERATOR_BINARY, '+');
		struct ast_node *exp = ast_pop();
		struct ast_node *term = ast_pop();
		ast_child_add(n, term);
		ast_child_add(n, exp);
		ast_push(n);
	}
	|
	expression '-' expression {
		struct ast_node *n = ast_create( AST_OPERATOR_BINARY, '-');
		struct ast_node *exp = ast_pop();
		struct ast_node *term = ast_pop();
		ast_child_add(n, term);
		ast_child_add(n, exp);
		ast_push(n);
	}
	|
	expression '*' expression {
		struct ast_node *n = ast_create(AST_OPERATOR_BINARY, '*');
		struct ast_node *exp = ast_pop();
		struct ast_node *term = ast_pop();
		ast_child_add(n, term);
		ast_child_add(n, exp);
		ast_push(n);
	}
	|
	expression '/' expression {
		struct ast_node *n = ast_create(AST_OPERATOR_BINARY, '/');
		struct ast_node *exp = ast_pop();
		struct ast_node *term = ast_pop();
		ast_child_add(n, term);
		ast_child_add(n, exp);
		ast_push(n);
	}
	|
	'(' expression ')' {
		struct ast_node *n = ast_create(AST_GROUP_EXPRESSIONS, 0);
		struct ast_node *child = ast_pop();
		ast_child_add(n, child);
		ast_push(n);
	}
	|
	factor
	;

factor:
	DD_CONSTANT_NUMBER { ast_push( ast_create(AST_NUMBER, $1) ); }
	|
	DD_CONSTANT_STRING { ast_push( ast_create(AST_STRING, $1) ); }
	|
	variable optional_variable_function {
		struct ast_node *arguments = ast_pop();
		struct ast_node *variable = ast_pop();
		if (arguments->node_type == AST_EMPTY) {
			ast_push(variable);
		}
		else {
			struct ast_node *n = ast_create(AST_FUNCTION_CALL, variable->value);
			ast_child_add(n, variable);
			ast_child_add(n, arguments);
			ast_push(n);
		}
	}
	;

variable_type:
	DD_KEYWORD_VOID { $$ = DD_KEYWORD_VOID; ast_push( ast_create(AST_VARTYPE_VOID, 0) ); }
	|
	DD_KEYWORD_INT { $$ = DD_KEYWORD_INT; ast_push( ast_create(AST_VARTYPE_INT, 0) ); }
	|
	DD_KEYWORD_FLOAT { $$ = DD_KEYWORD_FLOAT; ast_push( ast_create(AST_VARTYPE_FLOAT, 0) ); }
	|
	DD_KEYWORD_STRUCT DD_CONSTANT_SYMBOL {
		$$ = DD_KEYWORD_STRUCT;
		struct entry *e = symtable_entryat($2);
		if (!e) {
			fprintf(stderr, "yacc: struct variable type: could not find symtable entry at %d\n", $2);
			exit(-1);
		}
		ast_push( ast_create(AST_VARTYPE_STRUCT, struct_lookup(e->lexptr)) );
	}
	;

struct_parent:
	{ ast_push( ast_create(AST_EMPTY, 0) ); }
	|
	':' DD_CONSTANT_SYMBOL {
		struct entry *e = symtable_entryat($2);
		if (!e) {
			fprintf(stderr, "yacc: struct parent variable type: could not find symtable entry at %d\n", $2);
			exit(-1);
		}
		ast_push( ast_create(AST_STRUCT_PARENT, struct_lookup(e->lexptr)) );
	}
	;

optional_variable_function:
	{ ast_push( ast_create(AST_EMPTY, 0) ); }
	|
	'(' function_arguments_call ')' {
	}
	;

variable:
	DD_CONSTANT_SYMBOL {
		ast_push( ast_create(AST_IDENTIFIER, $1 ));
	}
	variable_additional
	;

variable_additional:
	| variable_additional '.' DD_CONSTANT_SYMBOL {
		struct ast_node *n = ast_pop();
		ast_child_add(n, ast_create(AST_IDENTIFIER, $3));
		ast_push(n);

		$$ = $3;
	}
	;

function_arguments_decleration:
	{ ast_push( ast_create(AST_GROUP, 0) ); }
	|
	variable_type DD_CONSTANT_SYMBOL function_arguments_decleration_additional {
		struct ast_node *n = ast_pop();
		struct ast_node *vartype = ast_pop();
		struct entry *esymbol = symtable_entryat($2);
		if (!esymbol) {
			fprintf(stderr, "yacc: function arguments declaration: could not find symtable entry at %d\n", $2);
			exit(-1);
		}
		esymbol->token = $1;
		ast_child_add_first(n, ast_create(AST_IDENTIFIER, $2));
		ast_push(n);
	}
	;

function_arguments_decleration_additional:
	{ ast_push( ast_create(AST_GROUP, 0) ); }
	|
	',' variable_type DD_CONSTANT_SYMBOL function_arguments_decleration_additional {
		struct ast_node *n = ast_pop();
		struct ast_node *vartype = ast_pop();
		struct entry *esymbol = symtable_entryat($3);
		if (!esymbol) {
			fprintf(stderr, "yacc: function arguments declaration: could not find symtable entry at %d\n", $3);
			exit(-1);
		}
		esymbol->token = $2;
		ast_child_add_first(n, ast_create(AST_IDENTIFIER, $3));
		ast_push(n);
	}
	;

function_arguments_call:
	{ ast_push( ast_create(AST_GROUP, 0) ); }
	|
	expression function_arguments_call_additional {
		struct ast_node *n = ast_pop();
		struct ast_node *expression = ast_pop();
		ast_child_add_first(n, expression);
		ast_push(n);
	}
	;

function_arguments_call_additional:
	{ ast_push( ast_create(AST_GROUP, 0) ); }
	|
	',' expression function_arguments_call_additional {
		struct ast_node *n = ast_pop();
		struct ast_node *expression = ast_pop();
		ast_child_add_first(n, expression);
		ast_push(n);
	}
	;
